#ifndef __LZMA_CODER__
#define __LZMA_CODER__

#include "bas.coder.h"
#include "../../lib3rd/lzma2/LzmaEnc.h"
#include "../../lib3rd/lzma2/LzmaDec.h"
#include "../../lib3rd/lzma2/Alloc.h"
#include "../../lib3rd/lzma2/Types.h"

namespace ak
{
	namespace coder
	{
		static void *SzBigAlloc(void *, size_t size) { return BigAlloc(size); }
		static void SzBigFree(void *, void *address) { BigFree(address); }
		static ISzAlloc g_BigAlloc = { SzBigAlloc, SzBigFree };
		static void *SzAlloc(void *, size_t size) { return MyAlloc(size); }
		static void SzFree(void *, void *address) { MyFree(address); }
		static ISzAlloc g_Alloc = { SzAlloc, SzFree };

		class LzmaDecoderLL : public CoderLL<>, UnkBase
		{
		protected:
			typedef CoderLL<> BaseT;
			CLzmaDec	_dec;
			UINT8		_props[LZMA_PROPS_SIZE];
			SIZE_T		_propbytes;
		public:
			HRESULT init_class( IUnknown * , IUnknown * ) { return S_OK;}
			UNKNOWN_IMP1_(IKBCoderLL);
		public:
			LzmaDecoderLL()
			{
				memset( &_dec, 0, sizeof(_dec) );
				LzmaDec_Init( &_dec );
			}
			STDMETHOD(Start)( IUnknown* pstm, LPCVOID lpConfig = 0, IKBCoderHook * hook = 0 )
			{
				RFAILED( BaseT::Start(pstm, hook) );
				LzmaDec_Init(&_dec);
				_propbytes = 0;
				return S_OK;
			}
			STDMETHOD(End)() 
			{
				if( _propbytes >= LZMA_PROPS_SIZE )
					LzmaDec_Free(&_dec, &g_Alloc);
				_propbytes = 0;
				LzmaDec_Init( &_dec );
				RFAILED( BaseT::End() );
				return S_OK;
			}
		protected:
			SIZE_T _CodeFrame( LPCVOID lpInBuf, SIZE_T cbIn, LPVOID lpOutBuf, SIZE_T & cbOut )
			{
				SIZE_T eat = 0;
				if( _propbytes < LZMA_PROPS_SIZE )
				{
					SIZE_T need = LZMA_PROPS_SIZE - _propbytes;
					eat = MIN(cbIn,need);
					memcpy( &_props[_propbytes], lpInBuf, need );
					lpInBuf = (LPCSTR)lpInBuf + eat;
					cbIn -= eat;
					_propbytes += eat;
					if( _propbytes >= LZMA_PROPS_SIZE )
					{
						SRes sr = LzmaDec_Allocate( &_dec, _props, LZMA_PROPS_SIZE, &g_Alloc );
						if( sr != SZ_OK ) return INVALID_CODE_SIZE;
					}
				}
				if( !cbIn ) return eat;
				SizeT nIn = cbIn, nOut = cbOut;
				ELzmaStatus status;
				SRes sr = LzmaDec_DecodeToBuf( &_dec, (Byte*)lpOutBuf, &nOut, (const Byte*)lpInBuf, &nIn, LZMA_FINISH_ANY, &status );
				if( sr != SZ_OK ) return INVALID_CODE_SIZE;
				cbOut = nOut;
				return nIn;
			}
		};

		struct BufferSeqOutStream : ISeqOutStream
		{
			LPVOID				Buffer;
			SIZE_T				Bytes;
			SIZE_T				Rem;
			std::vector<char>	OverflowHold;
			BufferSeqOutStream( LPVOID buf = 0, SIZE_T bytes = 0 )
			{
				Buffer = buf;
				Bytes = bytes;
				Rem = bytes;
				Write = write;
			}

			VOID Reset()
			{
				Buffer = 0;
				Bytes = 0;
				Rem = 0;
				OverflowHold.clear();
			}

			VOID BindBuffer( LPVOID buf, SIZE_T bytes )
			{
				Buffer = buf;
				Bytes = bytes;
				Rem = bytes;
			}

			SIZE_T	HoldBytes()
			{
				return OverflowHold.size();
			}

			LPCVOID HoldBase()
			{
				return HoldBytes() ? OverflowHold.data() : NULL;
			}

			SIZE_T FetchHolding( VOID * buf, SIZE_T want )
			{
				size_t c = MIN(want, OverflowHold.size() );
				if( !c ) return c;
				memcpy( buf, &OverflowHold[0], c );
				OverflowHold.erase(OverflowHold.begin(), OverflowHold.begin() + c );
				return c;
			}

			size_t _write( const void *buf, size_t size )
			{
				size_t fill = MIN(Rem,size);
				if( fill ) 
				{
					memcpy( (LPSTR)Buffer + Bytes - Rem, buf, fill );
					size -= fill;
					Rem -= fill;
					buf = (LPSTR) + fill;
				}
				if( size )
					OverflowHold.insert(OverflowHold.end(), (LPCSTR)buf, (LPCSTR)buf + size );
				return fill;
			}
			static size_t write(void *p, const void *buf, size_t size)
			{
				BufferSeqOutStream *self = (BufferSeqOutStream *)p;
				return self->_write( buf, size );
			}
		};
			

		class LzmaEncoderLL : public CoderLL<>, UnkBase
		{
		protected:
			typedef CoderLL<>	BaseT;
			CLzmaEncHandle		_handle;
			CLzmaEncProps		_prop;
			UINT8				_propbuf[LZMA_PROPS_SIZE];
			SizeT				_n_prop;
			BufferSeqOutStream	_stmout;
		public:
			HRESULT init_class( IUnknown * , IUnknown * ) { return S_OK;}
			UNKNOWN_IMP1_(IKBCoderLL);
		public:
			LzmaEncoderLL() : _handle(0), _n_prop(0)
			{
				LzmaEncProps_Init(&_prop);
				LzmaEncProps_Normalize(&_prop);
			}
			~LzmaEncoderLL()
			{
				if( !_handle ) return ;
				LzmaEnc_Destroy( _handle, &g_Alloc, &g_BigAlloc );
			}
			STDMETHOD(Start)( IUnknown* pstm, LPCVOID lpConfig = 0, IKBCoderHook * hook = 0 )
			{
				CLzmaEncProps * prop = lpConfig ? (CLzmaEncProps*)lpConfig : &_prop;
				RFAILED( BaseT::Start(pstm, hook) );
				_handle = LzmaEnc_Create( &g_Alloc );
				if( !_handle ) return E_OUTOFMEMORY;
				SRes sr = LzmaEnc_SetProps(_handle, prop);
				if( sr != SZ_OK ) return E_FAIL;
				SizeT nProp = LZMA_PROPS_SIZE;
				sr = LzmaEnc_WriteProperties( _handle, _propbuf, &nProp );
				if( sr != SZ_OK ) return E_FAIL;
				_n_prop = nProp;
				return S_OK;
			}
			STDMETHOD(End)() 
			{
				if( !_handle ) return S_OK;
				SIZE_T cbHold = _stmout.HoldBytes();
				if( cbHold )
				{
					RFAILED( __AfterDataProcessed( 0, 0, cbHold, _stmout.HoldBase()) );
				}
				LzmaEnc_Destroy( _handle, &g_Alloc, &g_BigAlloc );
				_handle = 0;
				_n_prop = 0;
				_stmout.Reset();
				RFAILED( BaseT::End() );
				return S_OK;
			}
		protected:

			struct BufferSeqInStream : ISeqInStream
			{
				LPCVOID Buffer;
				SIZE_T	Bytes;
				SIZE_T	Rem;
				BufferSeqInStream( LPCVOID buf, SIZE_T bytes )
				{
					Buffer = buf;
					Bytes = bytes;
					Rem = bytes;
					Read = read;
				}
				SRes _read( void *buf, size_t *size )
				{
					if( !Rem ) 
					{
						*size = 0;
						return SZ_OK;
					}
					size_t want = MIN(*size, Rem );
					memcpy( buf, (LPCSTR)Buffer + Bytes - Rem, want );
					Rem -= want;
					*size = want;
					return SZ_OK;
				}
				static SRes read( void *p, void *buf, size_t *size )
				{
					BufferSeqInStream * self = (BufferSeqInStream*)p;
					return self->_read( buf, size );
				}
			};

			SIZE_T _CodeFrame( LPCVOID lpInBuf, SIZE_T cbIn, LPVOID lpOutBuf, SIZE_T & cbOut )
			{
				SIZE_T eat = 0;
				if( _n_prop )
				{
					SIZE_T nExp = MIN(cbOut, _n_prop);
					memcpy(lpOutBuf, &_propbuf, nExp);
					lpOutBuf = (LPSTR)lpOutBuf + nExp;
					cbOut -= nExp;
					_n_prop -= nExp;
					if( !cbOut ) return eat;
				}

				if( _stmout.HoldBytes() )
				{
					size_t fill = _stmout.FetchHolding( lpOutBuf, cbOut );
					cbOut -= fill;
					lpOutBuf = (LPSTR)lpOutBuf + fill;
					if( !cbOut ) return eat;
				}				
				BufferSeqInStream stmin( lpInBuf, cbIn );
				_stmout.BindBuffer( lpOutBuf, cbOut );
				SRes sr = LzmaEnc_Encode( _handle, &_stmout, &stmin, 0, &g_Alloc, &g_BigAlloc );
				if( sr != SZ_OK ) return INVALID_CODE_SIZE;
				cbOut = _stmout.Bytes - _stmout.Rem;
				return stmin.Bytes - stmin.Rem;
			}
		};

	};



};



#endif